package com.dingwen.treasure.core.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.dingwen.treasure.base.pojo.vo.Result;
import com.dingwen.treasure.base.pojo.vo.ResultGenerator;
import com.dingwen.treasure.common.redis.service.RedisService;
import com.dingwen.treasure.core.annotation.EasyCache;
import com.dingwen.treasure.core.util.AspectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * (EasyCache)缓存切面 基于redis 实现 </br>
 * <p>通用业务逻辑处理：指定Key生成规则，进行缓存</p>
 *
 * @author dingwen
 * @date 2022/04/21
 */
@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
public class EasyCacheAspect {

    /**
     * redis key 连接符
     */
    private static final String KEY_APPEND = ":";

    /**
     * 默认返回值全类名
     */
    private static final String RETURN_DEFAULT_TYPE = "java.lang.Void";

    /**
     * redis 服务
     */
    private final RedisService redisService;

    /**
     * 原子整数 </br>
     * <p>环绕增强方法执行3次，优化key生成只执行一次</p>
     */
    private final AtomicInteger atomicInteger = new AtomicInteger(2);

    /**
     * redis key
     */
    private  String redisKey = StringUtils.EMPTY;


    /**
     * 连接点
     */
    private ProceedingJoinPoint joinPoint;

    /**
     * 注解信息
     */
    private EasyCache easyCache;


    /**
     * 全类名
     */
    private String fullClassName;

    /**
     * 方法名
     */
    private String methodName;

    /**
     * 方法入参
     */
    private Object[] args;

    /**
     * 初始化方法， 方便方法之间调用 </br>
     * <ol>
     *     <li>{@link ProceedingJoinPoint } 初始化链接点信息</li>
     *     <li>{@link EasyCache} 初始化，注解信息</li>
     *     <li>{@link SpelExpressionParser} Spring EL 表达式解析器 </li>
     * </ol>
     * //TODO 多线程优化
     * @param joinPoint 连接点
     */
    private void init(ProceedingJoinPoint joinPoint, EasyCache easyCache) {
        this.joinPoint = joinPoint;
        this.easyCache = easyCache;
        // Spring EL 表达式解析器

        // 全类名
        fullClassName = getClassName();
        // 方法名
        methodName = getMethodName();
        // 方法入参
        args = joinPoint.getArgs();
    }

    /**
     * 环绕增强
     *
     * @param joinPoint 连接点
     * @param easyCache te缓存
     * @return {@link Object}
     */
    @Around("@annotation(easyCache)")
    public Object doAround(ProceedingJoinPoint joinPoint, EasyCache easyCache) {

        // 返回值为空时不进行缓存逻辑处理
        if (RETURN_DEFAULT_TYPE.equals(easyCache.returnType().getName())) {
            log.error("[EasyCache],方法返回值为空不需要进行缓存处理");
            return null;
        }


        // 优化： 初始化&生成key只执行一次
       // 步长： 用于优化环绕方法中的生成 key 只进行一次操作
        int step = 3;
        if (atomicInteger.incrementAndGet() % step == 0) {
            // 1.连接点、注解、表达式解析器信息赋值（执行一次即可）
            init(joinPoint, easyCache);
            // 2. 生成key
            redisKey = generateKey();
        }


        // 3. 从缓存中获取数据
        Object cacheData = redisService.getCacheObject(redisKey);

        if (ObjectUtil.isNotNull(cacheData)) {
            if (easyCache.printedLog()) {
                log.info("[EasyCache],类：{},方法：{},key：{},缓存命中,方法入参：{},返回结果：{}",
                        fullClassName,
                        methodName,
                        redisKey,
                        args,
                        cacheData
                );
            }
            return ResultGenerator.genSuccessResult(cacheData, "EasyCache 缓存命中返回");
        }

        // 4.执行原方法获取返回值
        // 方法返回实时的返回数据
        Object returnObject;
        try {
            // 方法返回值类型
            returnObject = Convert.convert(easyCache.returnType(), joinPoint.proceed());
        } catch (Throwable e) {
            log.error("[EasyCache],类：{},方法：{},key：{},方法入参: {}尝试获取方法返回值类型或执行原方法发生异常，message：{}",
                    fullClassName,
                    methodName,
                    redisKey,
                    args,
                    e.getMessage()
            );
            return null;
        }


        if (ObjectUtil.isNull(returnObject)) {
            // 原方法执行返回结果为空，不进行缓存
            log.error("[EasyCache],类：{},方法：{},key：{},缓存未命中,执行原方法返回值为空，不进行缓存,方法入参: {}",
                    fullClassName,
                    methodName,
                    redisKey,
                    args
            );
            return null;
        }
        // 5.更新缓存

        if (easyCache.printedLog()) {
            log.info("[EasyCache],缓存key:{}，,有效期：{} {}", redisKey, easyCache.time(), easyCache.unit());
        }
        if (returnObject instanceof Result) {
            Result result = (Result) returnObject;
            Object data = result.getData();
            redisService.setCacheObject(redisKey, JSONUtil.toJsonStr(data), easyCache.time(), easyCache.unit());
            return ResultGenerator.genSuccessResult(data);
        }
        redisService.setCacheObject(redisKey, JSONUtil.toJsonStr(returnObject), easyCache.time(), easyCache.unit());
        return ResultGenerator.genSuccessResult(returnObject);
    }

    /**
     * 生成key <br>
     *
     * @return {@link String}
     */
    private String generateKey() {
        // 生成key的方法参数值
        String[] keyParamValues = getKeyParamValues();

        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(fullClassName)
                .append(KEY_APPEND)
                .append(methodName);


        // 设置默认参数拼接 key 规则 #p
        if (keyParamValues.length == 0) {
            keyParamValues[0] = easyCache.keyParams()[0];
        }

        Arrays.asList(keyParamValues)
                .forEach(param -> {
                    keyBuilder.append(KEY_APPEND);
                    keyBuilder.append(param);
                });

        // 是否进行日志打印
        if (easyCache.printedLog()) {
            log.info("[EasyCache],通过注解获取方法真实参数，生成缓存key，真实方法参数： {}", (Object) keyParamValues);
        }

        return keyBuilder.toString();
    }


    /**
     * 获取注解指定的，方法真实参数 <br>
     * <p>将注解中的占位符替换为真实请求参数（El表达式实现）</p>
     *
     * @return {@link String[]}
     */
    private String[] getKeyParamValues() {
        // 形式参数名称集合
        String[] formalParameterNames = AspectUtil.getFormalParameterNames(joinPoint);
        int length = easyCache.keyParams().length;
        String[] keyParamValues = new String[length];
        for (int i = length - 1; i >= 0; i--) {
            String paramPlaceholder = easyCache.keyParams()[i];
            Integer index = AspectUtil.getIndexForKeyParamsInFormalParameterNames(paramPlaceholder, formalParameterNames);
            keyParamValues[i] = AspectUtil.getValue(paramPlaceholder, formalParameterNames[index], args[index], String.class);
        }
        return keyParamValues;
    }


    /**
     * 获取类名
     *
     * @return {@link String}
     */
    private String getClassName() {
        return joinPoint.getSourceLocation().getWithinType().getName();
    }


    /**
     * 获取方法名
     *
     * @return {@link String}
     */
    private String getMethodName() {
        return joinPoint.getSignature().getName();
    }
}
